/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.burnoutcrew.reorderable

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.isOutOfBounds
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFirstOrNull
import kotlinx.coroutines.TimeoutCancellationException
// 从 DragGestureDetector 复制而来，因为在指针 API 准备好之前需要使用

/**
 * 等待指针移动超过阈值或手势被取消
 *
 * @param pointerId 要跟踪的指针ID
 * @param pointerType 指针类型（触摸、鼠标等）
 * @param onPointerSlopReached 当指针移动超过阈值时的回调函数
 * @return 如果手势被接受则返回 PointerInputChange，否则返回 null
 */
internal suspend fun AwaitPointerEventScope.awaitPointerSlopOrCancellation(
    pointerId: PointerId,
    pointerType: PointerType,
    onPointerSlopReached: (change: PointerInputChange, overSlop: Offset) -> Unit
): PointerInputChange? {
    // 如果指针已经抬起，则手势被取消
    if (currentEvent.isPointerUp(pointerId)) {
        return null // 指针已经被抬起，手势被取消
    }
    var offset = Offset.Zero
    // 获取对应指针类型的移动阈值
    val touchSlop = viewConfiguration.pointerSlop(pointerType)

    var pointer = pointerId

    while (true) {
        val event = awaitPointerEvent()
        // 查找当前跟踪的指针事件
        val dragEvent = event.changes.fastFirstOrNull { it.id == pointer } ?: return null
        // 如果事件已被消费，则手势被取消
        if (dragEvent.isConsumed) {
            return null
        } else if (dragEvent.changedToUpIgnoreConsumed()) {
            // 如果当前指针抬起，查找其他按下的指针
            val otherDown = event.changes.fastFirstOrNull { it.pressed }
            if (otherDown == null) {
                // 这是最后一个"抬起"事件
                return null
            } else {
                // 切换到新的按下的指针
                pointer = otherDown.id
            }
        } else {
            // 累计位置变化
            offset += dragEvent.positionChange()
            val distance = offset.getDistance()
            var acceptedDrag = false
            // 如果移动距离超过阈值
            if (distance >= touchSlop) {
                // 计算超过阈值的部分
                val touchSlopOffset = offset / distance * touchSlop
                onPointerSlopReached(dragEvent, offset - touchSlopOffset)
                // 检查事件是否被消费来确定是否接受拖拽
                if (dragEvent.isConsumed) {
                    acceptedDrag = true
                } else {
                    // 重置偏移量
                    offset = Offset.Zero
                }
            }

            // 如果接受了拖拽，返回事件
            if (acceptedDrag) {
                return dragEvent
            } else {
                // 等待最终事件传递阶段
                awaitPointerEvent(PointerEventPass.Final)
                // 检查事件是否被消费
                if (dragEvent.isConsumed) {
                    return null
                }
            }
        }
    }
}

/**
 * 等待长按或手势被取消
 *
 * @param initialDown 初始按下事件
 * @return 如果检测到长按则返回 PointerInputChange，否则返回 null
 */
internal suspend fun AwaitPointerEventScope.awaitLongPressOrCancellation(
    initialDown: PointerInputChange
): PointerInputChange? {
    var longPress: PointerInputChange? = null
    var currentDown = initialDown
    // 获取长按超时时间
    val longPressTimeout = viewConfiguration.longPressTimeoutMillis
    return try {
        // 等待第一次抬起或长按超时
        withTimeout(longPressTimeout) {
            var finished = false
            while (!finished) {
                val event = awaitPointerEvent(PointerEventPass.Main)
                // 如果所有指针都已抬起
                if (event.changes.fastAll { it.changedToUpIgnoreConsumed() }) {
                    // 所有指针都已抬起
                    finished = true
                }

                // 检查是否有事件被消费或超出边界
                if (
                    event.changes.fastAny {
                        it.isConsumed || it.isOutOfBounds(size, extendedTouchPadding)
                    }
                ) {
                    finished = true // 被取消
                }

                // 通过检查最终传递阶段来检查位置消费情况
                // 我们可以查看现有指针事件的最终传递阶段，因为它在上面检查的主要传递阶段之后
                val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
                if (consumeCheck.changes.fastAny { it.isConsumed }) {
                    finished = true
                }
                // 如果当前指针未抬起，更新长按事件
                if (!event.isPointerUp(currentDown.id)) {
                    longPress = event.changes.fastFirstOrNull { it.id == currentDown.id }
                } else {
                    // 查找新的按下指针
                    val newPressed = event.changes.fastFirstOrNull { it.pressed }
                    if (newPressed != null) {
                        currentDown = newPressed
                        longPress = currentDown
                    } else {
                        // 从技术上讲不应该发生，因为我们已经在上面检查过了
                        finished = true
                    }
                }
            }
        }
        null
    } catch (_: TimeoutCancellationException) {
        // 超时，表示检测到长按
        longPress ?: initialDown
    } catch (_: PointerEventTimeoutCancellationException){
        // 指针事件超时，表示检测到长按
        longPress ?: initialDown
    }
}

/**
 * 检查指针是否已抬起
 *
 * @param pointerId 要检查的指针ID
 * @return 如果指针已抬起返回 true，否则返回 false
 */
private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean =
    changes.fastFirstOrNull { it.id == pointerId }?.pressed != true

// 这个值是通过实验和常识确定的
// 我们不能使用零阈值，因为一些假设的桌面/移动设备可能发送具有非常高精度的指针事件
// （但我没有遇到任何发送精度低于1px的事件的设备）
private val mouseSlop = 0.125.dp
private val defaultTouchSlop = 18.dp // Android设备上的默认触摸阈值
private val mouseToTouchSlopRatio = mouseSlop / defaultTouchSlop

// TODO(demin): 考虑在 ViewConfiguration 类中添加此功能，当我们使 *PointerSlop* 函数公开后
// （参见文件顶部的注释）公开API后，我们应该去掉 `touchSlop / 144` 并返回绝对值 0.125.dp.toPx()
// 现在不可能实现，因为我们无法访问密度信息
/**
 * 根据指针类型获取相应的移动阈值
 *
 * @param pointerType 指针类型
 * @return 对应的移动阈值（像素）
 */
private fun ViewConfiguration.pointerSlop(pointerType: PointerType): Float {
    return when (pointerType) {
        PointerType.Mouse -> touchSlop * mouseToTouchSlopRatio
        else -> touchSlop
    }
}
